/*
 * test_net.cpp
 *
 *  Created on: Mar 31, 2014
 *      Author: killerg
 */

#include <event2/listener.h>
#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <event2/util.h>

#include <arpa/inet.h>

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>

#include "share/net_buffer.h"
#include "share/cs_msg.pb.h"
#include "share/util.h"

static void echo_read_cb(struct bufferevent *bev, void *ctx)
{
	struct evbuffer *input = bufferevent_get_input(bev);
	struct evbuffer *output = bufferevent_get_output(bev);
	printf("read_cb %d\n", evbuffer_get_length(input));

	char* line = evbuffer_readln(input, NULL, EVBUFFER_EOL_ANY);
	if (evutil_ascii_strcasecmp(line, "shut") == 0) {
		printf("shut\n");
		shutdown(bufferevent_getfd(bev), SHUT_RDWR);
	} else if (evutil_ascii_strcasecmp(line, "close") == 0) {
		printf("close\n");
		bufferevent_disable(bev, EV_WRITE|EV_READ);
		evutil_closesocket(bufferevent_getfd(bev));
	} else if (evutil_ascii_strcasecmp(line, "off") == 0) {
		timeval tv = {1,0};
		event_base_loopexit(bufferevent_get_base(bev), &tv);
		shutdown(bufferevent_getfd(bev), SHUT_RDWR);
		printf("off\n");
	} else {
		printf("echo\n");
		evbuffer_add_printf(output, "%s\n", line);
	}
	free(line);
}

static void echo_write_cb(struct bufferevent *bev, void *ctx)
{
	evbuffer *input = bufferevent_get_input(bev);
	evbuffer *output = bufferevent_get_output(bev);

	printf("write_cb %d\n", evbuffer_get_length(output));
}

static void echo_event_cb(struct bufferevent *bev, short events, void *ctx)
{
	printf("event_cb\n");

	if (events & BEV_EVENT_ERROR) {
		evutil_socket_t sock = bufferevent_getfd(bev);
		int err = evutil_socket_geterror(sock);
		if (err && err != ECONNRESET)
			printf("Error from bufferevent: %s\n", evutil_socket_error_to_string(err));

	}
	if (events & BEV_EVENT_EOF) {
		printf("eof\n");
	}

	if (events & (BEV_EVENT_ERROR | BEV_EVENT_EOF)) {
		bufferevent_free(bev);
	}

	if (events & BEV_EVENT_TIMEOUT) {
		printf("timeout and shutdown\n");
		shutdown(bufferevent_getfd(bev), SHUT_RDWR);
		bufferevent_free(bev);
	}
}

static void accept_conn_cb(struct evconnlistener *listener,
						evutil_socket_t fd, struct sockaddr *address, int socklen,
						void *ctx)
{
	struct event_base *base = evconnlistener_get_base(listener);
	struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);

	bufferevent_setcb(bev, echo_read_cb, echo_write_cb, echo_event_cb, NULL);
	bufferevent_enable(bev, EV_READ|EV_WRITE|EV_ET);
	timeval tv = {5,0};
	bufferevent_set_timeouts(bev, &tv, NULL);
}

static void accept_error_cb(struct evconnlistener *listener, void *ctx)
{
	struct event_base *base = evconnlistener_get_base(listener);
	int err = EVUTIL_SOCKET_ERROR();
	fprintf(stderr, "Got an error %d (%s) on the listener. "
			"Shutting down.\n", err, evutil_socket_error_to_string(err));

	switch (err) {
	case EMFILE:
	case ENFILE:
		break;
	default:
		event_base_loopexit(base, NULL);
		break;
	}
}

evconnlistener* start_acceptor(event_base* base, const char* ip, const uint16_t port, evconnlistener_cb acceptcb, evconnlistener_errorcb errcb, void* ctx) {
    sockaddr addr;
    int addr_len = sizeof(addr);
    if (evutil_parse_sockaddr_port(fmt_cstr("%s:%d", ip, port), &addr, &addr_len) < 0) {
        UTIL_LOG(E, "net_s %s_%d err: %s", __FUNCTION__, __LINE__, strerror(errno));
        return NULL;
    }

    evconnlistener *listener = evconnlistener_new_bind(
    									base, acceptcb, ctx,
    									LEV_OPT_CLOSE_ON_FREE|LEV_OPT_REUSEABLE,
    									-1, &addr, sizeof(addr));
    if (!listener) {
		perror("Couldn't create listener");
		return NULL;
    }
    evconnlistener_set_error_cb(listener, errcb);

    return listener;
}


bool test_levent_s() {
    event_base *base = event_base_new();
    if (!base) {
		puts("Couldn't open event base");
		return 1;
    }

    evconnlistener *listener = start_acceptor(base, "0.0.0.0", 9876, accept_conn_cb, accept_error_cb, NULL);
    assert(listener);

    while (!event_base_got_exit(base)) {
    	event_base_loop(base, EVLOOP_ONCE);
    }

    evconnlistener_free(listener);
    event_base_free(base);

	return true;
}
